package game;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class BetManager 
{
	private static final String BETS_BY_LOCATION_QUERY_STRING = "SELECT b FROM BetEntity b WHERE b.position = :betPosition";
	private static final String BETS_BY_PLAYER_ID_QUERY_STRING = "SELECT b FROM BetEntity b WHERE b.playerID = :playerId";
	private static final String BETS_BY_PLAYER_ID_AND_LOCATION_QUERY_STRING = "SELECT b FROM BetEntity b WHERE b.playerID = :playerId AND b.position = :betPosition";
	private static final String DEL_BET_BY_PLAYER_ID_AND_LOCATION_QUERY_STRING = "DELETE FROM BetEntity b WHERE b.playerID = :playerId AND b.position = :betPosition";
	private static final String DEL_ALL_BETS_QUERY_STRING = "DELETE FROM BetEntity b";
	
	private EntityManager myEntityManager;
	private EntityManagerFactory myEntityManagerFactory;
	
	Map<Long, Player> playerTable;
	public BetManager()
	{
		playerTable = new HashMap<Long, Player>();
		myEntityManagerFactory =  Persistence.createEntityManagerFactory("betpersist");
	    myEntityManager = myEntityManagerFactory.createEntityManager();
	    
	    //clear any existing bets out of the database
	    EntityTransaction clearDBTransaction = myEntityManager.getTransaction();
	    clearDBTransaction.begin();
	    myEntityManager.createQuery(DEL_ALL_BETS_QUERY_STRING).executeUpdate();
	    clearDBTransaction.commit();
	}
	
	public void close()
	{
		//Shutting Down
		myEntityManager.close();
		myEntityManagerFactory.close();
	}
	
	public void addPlayer(Player playerToAdd)
	{
		playerTable.put(playerToAdd.getID(), playerToAdd);
	}
	
	//PlaceBet
	//@param player The player who owns the bet
	//@param BetLocation The location where the bet is to be placed
	//@param AmountToBet The amount to place for the bet.  If a bet already exists, this amount will be added
	public void PlaceBet(Player player, BetPosition betLocation, double amountToBet)
	{
		
		BetEntity betToAdd = new BetEntity();
		betToAdd.setPlayerID(player.getID());
		betToAdd.setPosition(betLocation.name());

		double oldBetValue = RemoveBet(player, betLocation);
		
		double playerFunds = 0;
		
		if( oldBetValue >= amountToBet )
		{
			//Return the difference to the player (non-winning methods)
			player.receiveMoney(oldBetValue - amountToBet);
			betToAdd.setAmount(amountToBet);
		}
		else
		{
			//Attempt to acquire the difference from the player
			playerFunds = player.giveMoney(amountToBet - oldBetValue);
			betToAdd.setAmount( playerFunds + oldBetValue );
		}
		
		//Place the bet for the coverable amount (not necessarily the amount requested)
		EntityTransaction placeBetTransaction = myEntityManager.getTransaction();
		placeBetTransaction.begin();
		myEntityManager.persist(betToAdd);
		placeBetTransaction.commit();
	}
	
	//RemoveBet
	//@param player the player who owns the bet
	//@param BetLocation the location of the bet to be removed.
	//@returns the amount of money the player had on that location
	public double RemoveBet(Player player, BetPosition betLocation)
	{
		double toReturn = 0;
		
		//Find any bets for this location by this player and grab the total amount
		Query findExistingBetQuery = myEntityManager.createQuery(BETS_BY_PLAYER_ID_AND_LOCATION_QUERY_STRING);
		findExistingBetQuery.setParameter("betPosition", betLocation.name());
		findExistingBetQuery.setParameter("playerId", player.getID());
		//Yes I'm suppressing the warning, yes I'm doing it for a reason
		@SuppressWarnings("unchecked")
		List<BetEntity> existingBets = ((List<BetEntity>)(findExistingBetQuery.getResultList())); 
		for( BetEntity bet : existingBets )
		{
			toReturn += bet.getAmount();
		}

		//delete the bets
		Query delExistingBetQuery = myEntityManager.createQuery(DEL_BET_BY_PLAYER_ID_AND_LOCATION_QUERY_STRING);
		delExistingBetQuery.setParameter("playerId", player.getID());
		delExistingBetQuery.setParameter("betPosition", betLocation.name());
		EntityTransaction deleteTransaction = myEntityManager.getTransaction();
		deleteTransaction.begin();
		delExistingBetQuery.executeUpdate();
		deleteTransaction.commit();
		
		//return the total
		return toReturn;
	}
	
	//RemoveBet
	//@param betLocation
	//returns 0
	//Removes bet for all players given a location, returning the money to the respective owner
	public double RemoveBet(BetPosition betLocation)
	{
		double toReturn = 0;
		
		Query findBetsQuery = myEntityManager.createQuery(BETS_BY_LOCATION_QUERY_STRING);
		findBetsQuery.setParameter("betPosition", betLocation.name());
		//Yes I'm suppressing the warning, yes I have a good reason
		@SuppressWarnings("unchecked")
		List<BetEntity> existingBets = ((List<BetEntity>)(findBetsQuery.getResultList()));
		Player playerIter = null;
		for( BetEntity bet : existingBets)
		{
			playerIter = playerTable.get(bet.getPlayerID());
			playerIter.giveMoney(RemoveBet(playerIter, betLocation));
		}
		
		return toReturn;
	}
	
	public void Pay(BetPosition winningBet, double multiple)
	{
		//Pull set of players currently tracked
		// and iterate over them
		Query winningBetsQuery = myEntityManager.createQuery(BETS_BY_LOCATION_QUERY_STRING);
		winningBetsQuery.setParameter("betPosition", winningBet.name());
		
		
		@SuppressWarnings("unchecked")
		List<BetEntity> winningBets = ((List<BetEntity>) (winningBetsQuery.getResultList()));
		
		for( BetEntity bet : winningBets )
		{
			//Pay Floor(BetAmount * multiple)
			// ^^ The casino doesn't give out change :)
			playerTable.get(bet.getPlayerID()).giveWinnings(winningBet, Math.floor(bet.getAmount() * multiple));
		}
	}
	
	public void Take(BetPosition losingBet)
	{
		
		Query betsToDelQuery = myEntityManager.createQuery(BETS_BY_LOCATION_QUERY_STRING);
		betsToDelQuery.setParameter("betPosition", losingBet.name());
		
		@SuppressWarnings("unchecked")
		List<BetEntity> betsToDelList = ((List<BetEntity>) (betsToDelQuery.getResultList()));
		
		Player affectedPlayer = null;
		double amountLost = 0;
		for( BetEntity bet : betsToDelList)
		{
			affectedPlayer = playerTable.get(bet.getPlayerID());
			amountLost = RemoveBet(affectedPlayer, losingBet);
			affectedPlayer.notifyLoss(losingBet, amountLost);
		}
	}
	
	public double GetMoneyOnTable(Player player)
	{
		double moneyOnTable = 0;
		
		Query activeBetsQuery = myEntityManager.createQuery(BETS_BY_PLAYER_ID_QUERY_STRING);
		activeBetsQuery.setParameter("playerId", player.getID());
		
		@SuppressWarnings("unchecked")
		List<BetEntity> bets = ((List<BetEntity>) (activeBetsQuery.getResultList()));
		
		for( BetEntity bet : bets)
		{
			moneyOnTable += bet.getAmount();
		}
		return moneyOnTable;
	}
}
